package com.gotobject.junit.internal.runners.model;

import com.gotobject.junit.Load;
import com.gotobject.junit.Response;
import com.gotobject.junit.Throughput;
import org.junit.runner.Description;
import org.junit.runners.model.FrameworkMethod;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;

/**
 * @author Huascar A. Sanchez (huascar.sanchez@gmail.com)
 */
public enum PerformanceKey {
    LOAD(Load.class),
    RESPONSE(Response.class),
    THROUGHPUT(Throughput.class),
    UNKNOWN(null);

    private Class<? extends Annotation> anno;
    private String                      configName;
    
    PerformanceKey(Class<? extends Annotation> anno){
        this.anno = anno;
        configName = "<n/a>";
    }

    public static PerformanceKey from(FrameworkMethod method){
        final List<Class<? extends Annotation>> theOnes = Arrays.asList(Load.class, Response.class, Throughput.class);
        if(!isSingleTagged(method, theOnes)){
            throw new RuntimeException("Only ONE Perf annotation can be tagging a test method at a time.");
        }

        for(PerformanceKey eachLiteral : values()){
            for(Class<? extends Annotation> eachAnno : theOnes){
                if(eachAnno.equals(eachLiteral.getAnnotation()) && method.getAnnotation(eachAnno) != null){
                    switch (eachLiteral){
                        case LOAD:
                            eachLiteral.setConfigurationName(method.getAnnotation(Load.class).value());
                            break;
                        case RESPONSE:
                            eachLiteral.setConfigurationName(method.getAnnotation(Response.class).value());
                            break;
                        case THROUGHPUT:
                            eachLiteral.setConfigurationName(method.getAnnotation(Throughput.class).value());
                            break;
                        default:
                            return UNKNOWN;
                    }
                    return eachLiteral;
                }
            }
        }
        return UNKNOWN;
    }

    public static PerformanceKey from(Description desc){
		final List<Class<? extends Annotation>> annoClasses = Arrays.asList(Load.class, Response.class, Throughput.class);
        final Annotation anno = getAnnotation(desc);
        if(anno == null) return UNKNOWN;
        for(PerformanceKey eachLiteral : values()){
           for(Class<? extends Annotation> eachAnno : annoClasses){
               if(eachLiteral.getAnnotation().equals(eachAnno) && anno.annotationType().equals(eachAnno)){
                    switch (eachLiteral){
                        case LOAD:
                            // todo (Huascar): use #getAnnotation() to cast this instead of using a direct
                            // todo casting.
                            eachLiteral.setConfigurationName(Load.class.cast(anno).value());
                            break;
                        case RESPONSE:
                            eachLiteral.setConfigurationName(Response.class.cast(anno).value());
                            break;
                        case THROUGHPUT:
                            eachLiteral.setConfigurationName(Throughput.class.cast(anno).value());
                            break;
                        default:
                            return UNKNOWN;
                    }
                    return eachLiteral;
               }
           }
        }
        return UNKNOWN;
    }

    public static PerformanceKey like(String name){
        for(PerformanceKey each : values()){
            if(each.toString().equalsIgnoreCase(name)){
                return each;
            }
        }

        return UNKNOWN;
    }

    private static Annotation getAnnotation(Description desc) {
        final Annotation result;
        try {
            // ugly code: parse from string representation :(
            final String name = desc.getDisplayName();
            final StringTokenizer tokenizer = new StringTokenizer(name, "(");
            final String methodName = tokenizer.nextToken();
            final String className = tokenizer.nextToken(")").substring(1);
            final Class<?> clazz = Class.forName(className);
            final Method m = clazz.getMethod(methodName);
            // may be null
            final Load          l = m.getAnnotation(Load.class);
            final Response      r = m.getAnnotation(Response.class);
            final Throughput    t = m.getAnnotation(Throughput.class);

            result = (l != null) ? l : (r != null) ? r : (t != null) ? t : null;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        // in all error cases: no load for description available
        return result;
    }

    private Class<? extends Annotation> getAnnotation() {
        return anno;
    }

    public String getConfigName() {
        return configName;
    }

    private static boolean isSingleTagged(FrameworkMethod method, Iterable<Class<? extends Annotation>> annoClasses) {
        boolean already = false;
        boolean result  = false;
        for(Class<? extends Annotation> each : annoClasses){
            if(method.getAnnotation(each) != null){
                if(!already){
                    result  = true;
                    already = true;
                } else {
                    result = false;
                }
            }
        }
        if(!already){
            result = true;
        }

        return result;
    }

    private void setConfigurationName(String configName){
        this.configName = configName;
    }
}
